/* eslint-disable @typescript-eslint/ban-types */
import { _decorator, Node, UITransform, Widget, Size, Layers, ParticleAsset, AssetManager } from "cc";
import { IBaseController } from "db://app/base/BaseController";
import BaseView from "db://app/base/BaseView";
import Core from "db://app/Core";
import { GComponent, GObject, GTween, Transition, UIPackageManager } from "db://fairygui-xforge/FairyGUI";
import GPanel, { IGPanelOwner } from "db://fairygui-xforge/GPanel";
import { UIPanel } from "db://fairygui-xforge/UIPanel";
const { property, disallowMultiple } = _decorator;

type ComponentClass<T> = (new ()=> T) & { URL: string; };

type IPick<T> = {
    -readonly [P in keyof T]: T[P] extends Function
    ? T[P]
    : (T[P] extends Object
        ? IPick<T[P]>
        : T[P]);
};

export type IControllerInstance<C, T extends { [key in string]: any }> = IPick<C> & Readonly<{
    /**获取第一个事件回调的返回值 */
    emit<K extends keyof T>(key: K, ...args: Parameters<T[K]>): void;
    /**发射事件 */
    call<K extends keyof T & keyof T>(key: K, ...args: Parameters<T[K]>): ReturnType<T[K]>;
    /**注册事件回调 */
    on<K extends keyof T>(key: K, callback: (...args: Parameters<T[K]>)=> ReturnType<T[K]>, target?: any): void;
    /**注册一次性事件回调 */
    once<K extends keyof T>(key: K, callback: (...args: Parameters<T[K]>)=> ReturnType<T[K]>, target?: any): void;
    /**取消事件回调 */
    off(key: keyof T, callback: Function, target?: any): void;
    /**取消事件回调 */
    targetOff(target: any): void;
}>

interface IFairyGUIPop extends GComponent {
    onUIInit(): void;
    onUIUpdate(params: any): void;
    onUIHide(): void;
    onUIDestroy(): void;
}

export interface IFairyInnerPopData {
    cls: PopImpl<any, any>;
    params?: any;
    onShow?: ()=> void;
    onHide?: ()=> void;
    onError?: (e: any)=> void;
}

type PopImpl<TP extends (TG & IFairyGUIPop), TG extends GComponent> = ComponentClass<TP>;

export enum ViewFillMode {
    None,
    FullScreen,
    Center,
}

export abstract class FairyGUIView<T extends GComponent> extends BaseView implements IGPanelOwner {
    @property(UIPanel)
    public panel: UIPanel;

    protected Content: T;

    protected get fillMode(): ViewFillMode {
        if (this.isPop()) {
            return ViewFillMode.Center;
        }
        return ViewFillMode.FullScreen;
    }

    protected get hideWhenClickOutside(): boolean {
        return !this.isPage();
    }

    private _isDestroyed: boolean;

    protected set EnterTransition(transition: Transition) {
        if (transition) {
            this.enterAnimation = () => {
                return new Promise<void>((resolve) => {
                    transition.play(resolve);
                });
            };
        } else {
            this.enterAnimation = undefined;
        }
    }

    protected set ExitTransition(transition: Transition) {
        if (transition) {
            this.exitAnimation = () => {
                return new Promise<void>((resolve) => {
                    transition.play(resolve);
                });
            };
        } else {
            this.exitAnimation = undefined;
        }
    }

    public showPop<TP extends (TG & IFairyGUIPop), TG extends GComponent>(popCls: PopImpl<TP, TG>
        , params?: any, onShow?: ()=> void, onHide?: ()=> void, onError?: (err: any)=> void
    ): TP {
        return this.panel.root.showPop(popCls, params, onShow, onHide, onError);
    }

    public hidePop<TP extends (TG & IFairyGUIPop), TG extends GComponent>(popInst?: TP | string): void {
        this.panel.root.hidePop(popInst);
    }

    public showPops(pops: IFairyInnerPopData[], onComplete?: ()=> void) {
        const task = Core.inst.lib.task.createSync();
        if (pops.length === 0) {
            return task.start(onComplete);
        }

        pops.forEach((pop) => {
            task.add((next) => {
                this.showPop(pop.cls, pop.params, pop.onShow, () => {
                    pop.onHide && pop.onHide();
                    next();
                }, (err: any) => {
                    console.error(`${pop.cls} show error: ${err}`);
                    pop.onError && pop.onError(err);
                    next();
                });
            });
        });

        return task.start(onComplete);
    }

    public onLoad(): void {
        this.panel.owner = this;
        this.panel.node.on(UIPanel.Events.ON_UIPANEL_PRE_DESTROY, this.onNodeDestroy, this);
    }

    protected async loadContent(data?: any): Promise<string|null> {
        if (this.Content) {
            return null;
        }

        if (this.panel) {
            const loadError = await this.panel.loadPromise.then(()=>undefined as any).catch((err)=>{
                console.error("panel load fail: ", err);
                return err + "";
            });
            if (loadError) {
                return loadError;
            }
            if (this._isDestroyed) {
                return "view is destroyed";
            }
            const initError = this.onPanelLoaded(data);
            if (initError) {
                // 初始化失败还是显示界面但报错
                console.error("onPanelLoaded fail: ", initError);
            }
            return null;
        } else {
            return "panel not found";
        }
    }

    protected async beforeShow(next: (error?: string)=> void, data?: any) {
        const error = await this.loadContent(data);
        return next && next(error);
    }

    private onPanelLoaded(data?: any): string|undefined {
        this.Content = this.panel.Component as T;
        switch (this.fillMode) {
            case ViewFillMode.FullScreen:
                this.Content.full(true);
                break;
            case ViewFillMode.Center:
                this.Content.center(true);
                break;
            default:
                break;
        }
        try {
            this.onUIInit(this.Content, data);
        } catch (error) {
            console.error("onUIInit fail: ", error);
            return error + "";
        }
    }

    protected onUIInit(content: T, params: any): void {
        // tooverride
    }

    protected onUIUpdate(content: T, params: any): void {
        // tooverride
    }

    protected onUIDestroy(content: T): void {
        // tooverride
    }

    public onShow(params: any): void {
        try {
            this.onUIUpdate(this.Content, params);
        } catch (error) {
            console.error("onUIUpdate fail: ", this.name, error);
        }
    }

    private onNodeDestroy(): void {
        this.panel.node.off(UIPanel.Events.ON_UIPANEL_PRE_DESTROY, this.onNodeDestroy, this);
        this.destroyContent();
    }

    protected destroyContent(): void {
        this._isDestroyed = true;
        try {
            this.onUIDestroy(this.Content);
        } catch (error) {
            console.error("onUIDestroy fail: ", this.name, error);
        }
        GTween.kill(this.Content, false);
        this.Content = null;
    }

    protected onDestroy(): void {
        // tooverride
    }

    public onClickAnywhere(target: GObject, root: GPanel, canceled: boolean, closedSomthing: boolean): void {
        if (canceled || closedSomthing || !this.hideWhenClickOutside) {
            return;
        }
        if (target && target != root) return; // 点击到某个组件上了，不关闭
        this.hideWithAnimation();
    }
}

export function FairyPop<TG extends GComponent, C, T extends { [key in string]: any }>(cls: ComponentClass<TG>, Controller?: IBaseController<C, T>) {
    @disallowMultiple()
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    //@ts-ignore
    class FairyGUIPopImpl extends cls implements IFairyGUIPop {
        public onUIInit(): void {
            // tooverride
        }

        public onUIUpdate(params: any): void {
            // tooverride
        }
        public onUIHide(): void {
            // tooverride
        }
        public onUIDestroy(): void {
            // tooverride
        }

        public hide(includeAllAbove?: boolean): void {
            this.Root.hidePopup(this, !includeAllAbove);
        }

        protected get controller(): C {
            return Controller ? Controller.inst as any as C : null;
        }
    }

    return FairyGUIPopImpl;
}

export function FairyView<TG extends GComponent, C, T extends { [key in string]: any }>(cls: ComponentClass<TG>, Controller?: IBaseController<C, T>) {
    @disallowMultiple()
    class FairyGUIViewImpl extends FairyGUIView<TG> {
        public static get URL(): string {
            return cls.URL;
        }

        protected get controller(): IControllerInstance<C, T> {
            return Controller ? Controller.inst as any as IControllerInstance<C, T> : null;
        }
    }

    return FairyGUIViewImpl;
}

export function FairyScriptView<TG extends GComponent, C, T extends { [key in string]: any }>(packageName: string, itemName: string, Controller?: IBaseController<C, T>) {
    @disallowMultiple()
    class FairyGUIViewImpl extends FairyGUIView<TG> {
        public static get URL(): string {
            return `ui://${packageName}/${itemName}`;
        }

        public static preloadUI(bundleName: string, complete: (success:boolean)=>void, progress: (current: number, total: number, item: AssetManager.RequestItem)=>void) {
            UIPackageManager.load(packageName, progress, (err, pkg)=>{
                complete && complete(pkg != null);
            })
        }

        public static createUIOnComponent(parent: GComponent): GComponent {
            const cmp = new GComponent();
            parent.addChild(cmp);
            cmp.full(true);
            cmp.node.active = false;

            const panel = cmp.node.addComponent(UIPanel);
            panel.packageName = packageName;
            panel.itemName = itemName;
            panel.setRoot(cmp)

            const impl = cmp.node.addComponent(this);
            impl.panel = panel;
            return cmp;
        }

        public static createUINode(parent: Node): Node {
            const parentTransform = parent.getComponent(UITransform);
            const size = parentTransform && parentTransform.contentSize || new Size(100, 100);

            const root = new Node();
            root.active = false;
            root.layer = Layers.Enum.UI_2D;
            const rootTrans = root.addComponent(UITransform);
            rootTrans.setContentSize(size);
            const widget = root.addComponent(Widget);
            widget.isAlignLeft = widget.isAlignRight = widget.isAlignTop = widget.isAlignBottom = true;
            widget.left = widget.right = widget.top = widget.bottom = 0;

            const node = new Node();
            node.parent = root;
            node.layer = Layers.Enum.UI_2D;
            const nodeTrans = node.addComponent(UITransform);
            nodeTrans.setContentSize(size);
            const subWidget = node.addComponent(Widget);
            subWidget.isAlignLeft = subWidget.isAlignRight = subWidget.isAlignTop = subWidget.isAlignBottom = true;
            subWidget.left = subWidget.right = subWidget.top = subWidget.bottom = 0;

            const panel = node.addComponent(UIPanel);
            panel.packageName = packageName;
            panel.itemName = itemName;

            const impl = root.addComponent(this);
            impl.panel = panel;
            return root;
        }

        protected get controller(): IControllerInstance<C, T> {
            return Controller ? Controller.inst as any as IControllerInstance<C, T> : null;
        }
    }
    return FairyGUIViewImpl;
}
